Skip to content

feat: RealUnit wallet-to-wallet (W2W) gasless transfer flow#687

Open
TaprootFreak wants to merge 5 commits into
feature/ocp-pay-flowfrom
feature/w2w-transfer
Open

feat: RealUnit wallet-to-wallet (W2W) gasless transfer flow#687
TaprootFreak wants to merge 5 commits into
feature/ocp-pay-flowfrom
feature/w2w-transfer

Conversation

@TaprootFreak

@TaprootFreak TaprootFreak commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Summary

Phase 2 Baustein 3 — RealUnit wallet-to-wallet (W2W) transfer: send REALU to another wallet, recipient picked via QR scan or manual entry. Implements #684 (umbrella #666).

The transfer is gasless via EIP-7702 — DFX pays gas from a dedicated W2W gas wallet — so the app signs an EIP-712 delegation + an EIP-7702 authorization, exactly like the existing SOFTWARE gasless sell confirm (real_unit_sell_payment_info_service.dart). It reuses eip712_signer.dart / eip7702_signer.dart and the wallet unlock/lock boundary; it is not the bitbox raw-tx path.

Flow (Page + Cubit per step, separate state files):

  1. Recipient — scan a wallet QR or paste/type an EVM address; client-side checksum validation for UX only (the API is the final authority). An ethereum: EIP-681 URI is normalized to the bare address.
  2. Amount — whole REALU shares (REALU decimals = 0); the available balance is read via the shared balance watcher and the over-balance guard is local UX only.
  3. Confirm — review recipient + amount.
  4. Process — capability gate (software-only signing) → PUT /transfer → sign EIP-712 delegation + EIP-7702 authorization → PUT /transfer/:id/confirm → success (txHash) / typed failure.

Typed failures rendered as states (no error-string parsing): unsupported wallet (debug/BitBox), signature cancelled, invalid request (API 400/404 — invalid recipient / self-transfer / token-contract recipient / insufficient REALU), and gas-funding-unavailable (API ServiceUnavailable 503 → friendly "temporarily unavailable", REALU untouched).

Scanner reuse (no duplication)

The scanner from #674 was an inline MobileScanner in PayScanPage. Extracted a shared lib/widgets/scanner/qr_scanner_view.dart (the camera/MethodChannel wrapper) and refactored both the pay scan page and the new send recipient page onto it — each flow keeps its own decode logic (LNURL vs EVM address). No scanner code is duplicated.

API / decision authority

Consumes DFXswiss/api#3820 (pair-PR, backend lands first): PUT /v1/realunit/transfer + PUT /v1/realunit/transfer/:id/confirm. The app renders API-signaled outcomes and does not duplicate backend KYC/registration/limit/eligibility logic.

Branch / stacking

Branched from feature/ocp-pay-flow (#674) to reuse the scanner without duplication; PR base is staging. Stacked on #674 (scanner) — review/merge #674 first; this diff will shrink once #674 merges to staging.

Tests / gates

  • flutter analyze: 0 issues.
  • flutter test --coverage --exclude-tags golden: all pass; 100% scoped coverage on every new file (no coverage:ignore).
  • flutter test --tags golden: golden tests + baselines for the new screens.
  • dart format (repo config: page_width 100, trailing_commas preserve): clean.

Goldens regenerate pending on the runner: baselines here were rendered locally on macOS and will mismatch the CI runner; the Golden Regenerate workflow is being dispatched so the runner pushes authoritative baselines.

Stays Draft (no ready-for-review, no merge).

@TaprootFreak TaprootFreak marked this pull request as ready for review June 4, 2026 08:57
@TaprootFreak TaprootFreak force-pushed the feature/w2w-transfer branch from e4925f9 to 183b7a4 Compare July 13, 2026 15:11
@TaprootFreak TaprootFreak changed the base branch from staging to feature/ocp-pay-flow July 13, 2026 15:11
@TaprootFreak TaprootFreak force-pushed the feature/w2w-transfer branch from cd36446 to 532d1cc Compare July 13, 2026 16:13
Add a wallet-to-wallet send flow that transfers REALU to another wallet
(recipient via QR scan or manual entry), consuming the gasless EIP-7702
transfer endpoints from DFXswiss/api#3820.

The transfer is gasless: DFX pays gas from a dedicated W2W gas wallet via
EIP-7702, so the app signs an EIP-712 delegation + an EIP-7702 authorization
exactly like the SOFTWARE gasless sell confirm — reusing eip712_signer.dart /
eip7702_signer.dart and the wallet unlock/lock boundary. The capability gate
(software-only signing) surfaces a dedicated unsupported state for debug/BitBox
wallets; the flow is not otherwise branched on wallet type.

- Steps: enter/scan recipient -> amount (whole REALU shares, vs available
  balance) -> confirm -> sign + PUT /transfer then /transfer/:id/confirm ->
  success (txHash) / typed failures (unsupported wallet, signature cancelled,
  invalid request, gas-funding-unavailable from the API 503).
- Page + Cubit per step with separate state files.
- Reuse the QR scanner: extract a shared QrScannerView wrapping MobileScanner
  and refactor the pay scan page onto it (no scanner duplication).
- RealUnitTransferService (extends DFXAuthService) with the two endpoints,
  fromJson DTOs, and typed exceptions enumerated in exception_surface_test.
- Navigation: AppRoutes.send + GoRoute('/send') + a dashboard Send action.
- i18n: new keys in both ARBs, German + English.
- bloc_test/mocktail cubit tests for every step + typed failure, widget tests
  for the pages, 100% scoped coverage, golden tests + baselines.
Add widget/unit tests for the changed lib lines that the existing pay +
send suites did not yet exercise:

- DashboardActions: render + tap-routes the buy/sell/pay/send action
  buttons, covering the four Expanded(ActionButton) subtrees and their
  onPressed push closures (incl. the new send button).
- setupServices: resolve the newly registered RealUnitPayService and
  RealUnitTransferService factories, covering their registration and
  construction closures in di.dart.
- routerConfig /pay and /send routes: drive the real router to each route
  so the GoRoute builder closures returning PayScanPage / SendRecipientPage
  are executed.

AppRoutes.pay and AppRoutes.send are compile-time const fields (no
instrumentable line); they are exercised at runtime by the above tests.
Regenerate the three dashboard goldens that include the action row so
they show the four-button layout (buy/sell/pay/send). Baselines taken
from the self-hosted VR runner output (testImage).
@TaprootFreak TaprootFreak force-pushed the feature/w2w-transfer branch from 532d1cc to 63743cc Compare July 13, 2026 16:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant